package br.com.intelsys.basics.bfactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * Ate uma fabrica precisa ser abstraida, com isso temos o padrao Abstract
 * Factory.
 * 
 * Use esta "fabrica de fabricas" para recuperar uma fabrica de beans especifica
 * para a sua necessidade.
 * 
 * Assim voce pode mudar a fabrica (e por possivel consequencia a implementacao
 * de seus beans) sem precisar alterar seu sistema.
 * 
 * Esta fabrica conta com um arquivo de configuracao chamado factoryManager.xml
 * Ele deve esta no CLASS_PATH da aplicacao para ser encontrado. Exemplo:
 * WEB-INF/classes/factoryManager.xml
 * 
 * Um exemplo de configuracao seria:
 * 
 * <factory> <spring> <!-- configuracoes do spring --> <!-- onde esta o
 * applicationContext.xml do spring -->
 * <application-context>WEB-INF/applicationContext.xml</application-context>
 * </spring> </factory>
 * 
 * @author Patrick Givisiez
 * 
 */
@SuppressWarnings("deprecation")
public final class FactoryManager {

	private static final String FILE_NAME = "factoryManager.xml";

	protected static final Log log = LogFactory.getLog(FactoryManager.class);

	private static FactoryManager factoryManager;
	private static BFactory bFactory;
	private static SpringMvc springMvc;

	private static HashMap<String, String> factoryConfig;
	private final String CONFIG_SPRING_APPLICATION_CONTEXT = "application-context";

	private FactoryManager() {
		// Hey, I am a singleton!
	}

	/**
	 * Como esta fabrica eh uma singleton, use este metdo para pegar uma
	 * instancia.
	 * 
	 * @return Instancia de FactoryManager
	 */
	public static FactoryManager getInstance() {
		if (FactoryManager.factoryManager == null) {
			FactoryManager.factoryManager = new FactoryManager();
		}
		return (FactoryManager.factoryManager);
	}

	/**
	 * Recupera uma fabrica para criar Beans.
	 * 
	 * @return Fabrica de Beans.
	 */
	public BFactory getBFactory() {
		if (FactoryManager.bFactory == null) {
			this.loadFactoryConfig(false);
			FactoryManager.bFactory = new BFactorySpring(new XmlBeanFactory(new ClassPathResource(
					FactoryManager.factoryConfig.get(CONFIG_SPRING_APPLICATION_CONTEXT))));
		}
		return (FactoryManager.bFactory);
	}

	/**
	 * Recupera uma fabrica para criar Beans.
	 * 
	 * @return Fabrica de Beans.
	 */
	public SpringMvc getSpringMvc() {
		if (FactoryManager.springMvc == null) {
			this.loadFactoryConfig(true);
		}
		return (FactoryManager.springMvc);
	}

	/**
	 * Procura pelos arquivos de configuracao
	 * 
	 * @param file
	 *            Arquivo que ira ser procurado
	 * @return instancia do arquivo
	 */
	private InputStream searchFileConf(String file) {
		InputStream ret;
		try {
			ret = this.getClass().getClassLoader().getResourceAsStream(file);
			log.debug("Encontrado o arquivo de configuracao em: " + file);
		} catch (Exception e) {
			log.error("Nao foi possivel encontrar o arquivo de configuracao " + file + ". Verifique se ele esta em seu classpath.");
			ret = null;
			e.printStackTrace();
		}
		return (ret);
	}

	/**
	 * Carrega as configuracoes da Factory
	 */

	private void loadFactoryConfig(boolean loadSpringMvc) {
		InputStream iStream;
		iStream = this.searchFileConf(FILE_NAME);
		log.info("Configuracao factoryManager.xml encontrada.");
		try {
			this.readFactoryManager((new SAXBuilder()).build(iStream), loadSpringMvc);
		} catch (NullPointerException e) {
			e.printStackTrace();
			log.error("Nao foi possivel acessar o arquivo de configuracao de banco " + FILE_NAME
					+ ". Verifique se ele esta em seu classpath");
		} catch (IOException e) {
			e.printStackTrace();
			log.error("Nao foi possivel acessar o arquivo de configuracao de banco " + FILE_NAME
					+ ". Verifique se ele esta em seu classpath");
		} catch (JDOMException e) {
			e.printStackTrace();
			log.error("Existe algum erro no arquivo de configuracao de banco " + FILE_NAME);
		}
	}

	/**
	 * Faz o parse do arquivo de configuracao da fabrica
	 * 
	 * @param mainDoc
	 *            SAX XML Document
	 * 
	 */
	@SuppressWarnings("unchecked")
	private void readFactoryManager(Document mainDoc, boolean loadSpringMvc) {
		final String TMP_SYSTEM = "system";
		final String TMP_ROOT_PATH = "root-path";
		final String TMP_SPRING = "spring";
		final String TMP_WEBREQUEST = "webrequest";
		final String TMP_CLASS = "class";
		Element eChild, eChild2;
		List<Element> lChild2;
		Iterator<Element> i, i2;
		Element dwMaker = mainDoc.getRootElement();
		List<Element> elementList = dwMaker.getChildren();
		i = elementList.iterator();

		FactoryManager.factoryConfig = new HashMap<String, String>();

		log.info("Parsing [factoryManager.xml].");
		while (i.hasNext()) {
			eChild = i.next();
			if (eChild.getName().equals(TMP_SYSTEM)) {
				lChild2 = eChild.getChildren();
				i2 = lChild2.iterator();
				while (i2.hasNext()) {
					eChild2 = i2.next();
					if (eChild2.getName().equals(TMP_ROOT_PATH)) {
						FactoryManager.factoryConfig.put(TMP_ROOT_PATH, eChild2.getText());
					}
				}
			} else if (eChild.getName().equals(TMP_SPRING)) {
				lChild2 = eChild.getChildren();
				i2 = lChild2.iterator();
				while (i2.hasNext()) {
					eChild2 = i2.next();
					if (eChild2.getName().equals(CONFIG_SPRING_APPLICATION_CONTEXT)) {
						log.info("String Application Context encontrado.");
						FactoryManager.factoryConfig.put(CONFIG_SPRING_APPLICATION_CONTEXT, eChild2.getText());
					} else if (loadSpringMvc && eChild2.getName().equals(TMP_WEBREQUEST)) {
						springMvc = new SpringMvc(eChild2.getAttribute(TMP_CLASS).getValue());
					}
				}
			}
		}
		if (FactoryManager.factoryConfig.containsKey(CONFIG_SPRING_APPLICATION_CONTEXT)) {
			FactoryManager.factoryConfig.put(CONFIG_SPRING_APPLICATION_CONTEXT, FactoryManager.factoryConfig.get(TMP_ROOT_PATH)
					+ FactoryManager.factoryConfig.get(CONFIG_SPRING_APPLICATION_CONTEXT));
		}
	}

}
